#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import platform

from oracle import logger
from oracle.common.backup_common import get_ip_port_from_address
from oracle.common.constants import SliceConstant, ShellAuthTypeEnum, WindowsRegistryParentDirectory, ErrorCode, \
    InstanceOnlineStatus, OracleInstallType, OracleIsASM, RETURN_INVALID, ScriptExitCode, WindowsRegistryConstantString
from oracle.common.sql_common import get_local_listener
from oracle.common.windows_common import execute_windows_cmd, get_registry, check_oracle_install_type, \
    list_registry_directory, windows_switch_user, windows_switch_user_end, execute_windows_cmd_with_low_auth_user

if platform.system().lower() == "windows":
    import winreg


class OracleWindows:
    _db_user = ""
    _db_password = ""

    def __init__(self, pid: str, db_user: str = "", db_password: str = ""):
        self._pid = pid
        self._db_user = db_user
        if db_password:
            self._db_password = f'"{db_password}"'

    def oracle_info(self, db_name):
        """
        遍历注册表，筛选出所有数据库的注册表并获取相应的oracle_home
        根据oracle_home到对应路径用sqlplus获取相应信息
        :param db_name: str, 数据库名，只有数据库名和该入参相等的数据库信息会被返回
        :return: 返回错误码(int)，数据库信息列表(list)
        """
        logger.info("Execute oracle_info start! pid = %s", self._pid)
        database_list = []
        logger.info("Start to list all sub-directories in the registry of SOFTWARE\\ORACLE, pid = %s", self._pid)
        oracle_registries = list_registry_directory("SOFTWARE\\ORACLE")

        # 获取符合db_name的数据库实例
        logger.info("Start to list all home_path of the databases whose db_name is matched. pid = %s", self._pid)
        ret, queried_database_home_env = list_queried_database_home_env(self._pid, oracle_registries, db_name,
                                                                        self._db_user, self._db_password)
        if ret != ScriptExitCode.SUCCESS:
            logger.error("Get queried home_path of matched databases FAILED! pid = %s", self._pid)
            return ret, database_list
        for path_dict in queried_database_home_env:
            home_path = path_dict.get("oracle_home_path", "")
            base_path = path_dict.get("oracle_base_path", "")
            instance_name = path_dict.get("oracle_sid", "")
            db_basic_info = get_database_info(self._pid, instance_name, self._db_user, self._db_password)
            if len(db_basic_info) != SliceConstant.LENGTH_OF_BASIC_INFO:
                logger.error("Get basic info of the database FAILED! pid = %s", self._pid)
                return RETURN_INVALID, database_list
            instance_name = db_basic_info.get("instance_name", "")
            if db_basic_info.get("status", "") == "OPEN":
                db_status = InstanceOnlineStatus.INSTANCE_ONLINE
            else:
                db_status = InstanceOnlineStatus.INSTANCE_OFFLINE
            db_version = db_basic_info.get("version", "")
            db_auth_type = db_basic_info.get("auth_type", "")

            db_type = check_oracle_install_type()
            if db_type > OracleInstallType.DEFAULT:
                is_asm = OracleIsASM.ASM_INSTANCE
            else:
                is_asm = OracleIsASM.NON_ASM_INSTANCE
            db_info = {
                "version": db_version,
                "inst_name": instance_name,
                "db_name": db_name.lower(),
                "state": db_status,
                "auth_type": db_auth_type,
                "db_role": db_type,
                "oracle_home": home_path,
                "oracle_base": base_path,
                "is_asm_inst": is_asm
            }
            database_list.append(db_info)
            logger.info("Database with instance_name = %s is added to the list! pid = %s", instance_name, self._pid)
        return ErrorCode.SUCCESS.value, database_list

    def oracle_test_connection(self, params):
        """
        检查数据库连通性
        :param params: (dict)一个字典，包含InstanceName, AppName, UserName, Password, OracleInstallUser, GridInstallUser
        :return: 返回错误码信息(int)
        """
        logger.info("Execute oracle_test_connection start! pid = %s", self._pid)
        instance_name = params["InstanceName"]
        # 检查该instance是否存在
        check_cmd = "cmd /c sqlplus /nolog".split()
        input_cmd = f"conn {self._db_user}/ as sysdba\n" \
                    f"{self._db_password}"
        os.environ["ORACLE_SID"] = instance_name
        os.environ["NLS_LANG"] = "American_America.US7ASCII"
        ret_code, result = execute_windows_cmd_with_low_auth_user(self._pid, check_cmd, input_cmd)
        if not ret_code:
            logger.error(f"Execute sqlplus login FAILED! Pid = {self._pid}")
            return RETURN_INVALID
        if "ERROR" in result:
            logger.error(f"The instance {instance_name} does NOT exist! Pid = {self._pid}")
            return RETURN_INVALID

        # 检查database_status
        logger.info("Check database_status start! pid = %s", self._pid)
        db_basic_info = get_database_info(self._pid, instance_name, self._db_user, self._db_password)
        if len(db_basic_info) != SliceConstant.LENGTH_OF_BASIC_INFO:
            logger.error("Cannot get basic info of the database! pid = %s", self._pid)
            return RETURN_INVALID
        db_status = db_basic_info.get("status", "")
        if db_status != "OPEN":
            logger.error("Instance status is: %s, not OPEN! pid = %s", db_status, self._pid)
            return ErrorCode.ERROR_INSTANCE_NOSTART.value

        # 检查open_mode
        logger.info("Check open_mode start! pid = %s", self._pid)
        db_open_mode = get_db_open_mode(self._pid, instance_name, self._db_user, self._db_password)
        if not db_open_mode:
            logger.error("Cannot get the open_mode of the database! pid = %s", self._pid)
            return RETURN_INVALID
        if "READ" not in db_open_mode:
            logger.error("The open_mode of the database is not READ! pid = %s", self._pid)
            return RETURN_INVALID
        logger.info("Execute oracle_test_connection SUCCESS! pid = %s", self._pid)
        return ErrorCode.SUCCESS.value

    def check_oracle_base(self, db_name):
        """
        查询oracle base目录下是否有与数据库名对应的子目录，是否有和db_name相同的目录名
        :param db_name: 指定的db_name，检查base目录下是否有与此相同名称的子目录
        :return: 返回错误码，对应子目录
        """
        logger.info("Start to check oracle base! pid = %s", self._pid)
        result = []
        base_path_list = []
        reg_list = list_registry_directory("SOFTWARE\\ORACLE")
        for reg in reg_list:
            if reg.startswith("SOFTWARE\\ORACLE\\KEY_OraDB", SliceConstant.HEAD_OF_STRING_OR_LIST) or \
                    reg.startswith("SOFTWARE\\ORACLE\\KEY_OraDb", SliceConstant.HEAD_OF_STRING_OR_LIST):
                ret, base_path = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE, reg, "ORACLE_BASE")
                if not ret:
                    logger.warning(f"get oracle base error:{reg},{self._pid}")
                    continue
                base_path_list.append(os.path.join(base_path + "\\diag\\rdbms"))
        if not base_path_list:
            logger.error("Get ORACLE_BASE failed! pid = %s", self._pid)
            return ErrorCode.ERROR_SCRIPT_ORACLEBASE_LOST.value, result
        logger.info("Start to check if there is sub-directory that has the same name with db_name! pid = %s", self._pid)
        for base_path in base_path_list:
            sub_path_list = os.listdir(base_path)
            for sub_path in sub_path_list:
                if db_name.lower() == sub_path.lower():
                    result.append(sub_path.lower())
                    logger.info("Add the sub_path (%s) into the list! pid = %s", sub_path.lower(), self._pid)
        if not result:
            logger.error("No sub-directory match the db_name! pid = %s", self._pid)
            return RETURN_INVALID, result
        logger.info("Execute check_oracle_base SUCCESS! pid = %s", self._pid)
        return ErrorCode.SUCCESS.value, "\n".join(result)

    def oracle_get_ip_and_port(self, login_params):
        """
        获取ip和端口
        :return: 返回是否获取成功(bool)，和一个包含多个字典的列表(list)，字典包括ip和port两个字段
        """
        logger.info("Execute oracle_get_ip_and_port start! pid = %s", self._pid)
        ip_port_list = []
        local_listener = get_local_listener(self._pid, login_params)
        if 'ADDRESS=' in local_listener:
            ip_info, port = get_ip_port_from_address(local_listener)
            ip_port_list.append({'ip': ip_info, 'port': port})
            logger.info(f"Execute oracle_get_ip_and_port SUCCESS, ip_port_list={ip_port_list}, pid={self._pid}.")
            return True, ip_port_list
        registry_list = list_registry_directory("SOFTWARE\\ORACLE")
        oracle_home_path = ""
        for registry in registry_list:
            # 遍历注册表中的每一个数据库并获取其ip和port
            if registry.startswith("SOFTWARE\\ORACLE\\KEY_OraGI"):
                ret, oracle_grid_home = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE, registry,
                                                     "ORACLE_HOME")
                if not ret:
                    logger.error(f"Cannot get grid home path! Pid = {self._pid}")
                    return False, ip_port_list
                oracle_home_path = oracle_grid_home
            else:
                continue
        if not oracle_home_path:
            return True, ip_port_list

        target_home_path = f"{oracle_home_path}\\BIN"
        ret, target_host, target_port = get_ip_and_port(self._pid, target_home_path, local_listener)
        if not ret:
            logger.error("Get oracle ip and port FAILED! pid = %s", self._pid)
            return False, ip_port_list
        ip_port_list.append({"ip": target_host, "port": target_port})

        logger.info(f"Execute oracle_get_ip_and_port SUCCESS, ip_port_list={ip_port_list}, pid={self._pid}.")
        return True, ip_port_list

    def oracle_asm_info(self):
        logger.info("Execute oracle_asm_info start! pid = %s", self._pid)
        asm_info_list = []
        # 检查db_type
        db_type = check_oracle_install_type()
        if db_type == OracleInstallType.DEFAULT:
            logger.error("Not a rac or asm node, cannot get asm info! pid = %s", self._pid)
            return RETURN_INVALID, asm_info_list

        ret, asm_info = get_asm_instance_name(self._pid)
        if not ret:
            logger.error("Query ASM instance name FAILED! pid = %s", self._pid)
            return RETURN_INVALID, asm_info_list
        for inst_name in asm_info:
            ret_code, auth_type = get_auth_type(self._pid, inst_name, is_asm=True)
            if not ret_code:
                logger.error(f"Get asm auth_type FAILED! Pid = {self._pid}")
                return RETURN_INVALID, asm_info_list
            asm_dict = {
                "inst_name": inst_name,
                "auth_type": auth_type,
                "is_cluster": db_type
            }
            asm_info_list.append(asm_dict)
        logger.info("Execute oracle_asm_info SUCCESS! pid = %s", self._pid)
        return ErrorCode.SUCCESS.value, asm_info_list

    def oracle_rac_info(self):
        """
        获取Oracle RAC信息
        :return: 返回错误码(int)，以及集群信息(dict)，包含cluster_name, cluster_ip, cluster_type和domain_name
        """
        logger.info("Function oracle_rac_info start!")
        rac_info = {}
        # 检查db_type
        logger.info("Check db_type start!")
        db_type = check_oracle_install_type()
        if db_type != OracleInstallType.RAC:
            logger.error("Oracle is not a RAC, cannot get RAC information!")
            return RETURN_INVALID, rac_info

        # 获取oracle的home_path
        grid_home_path = ""
        registry_list = list_registry_directory("SOFTWARE\\ORACLE")
        for registry in registry_list:
            if registry.startswith("SOFTWARE\\ORACLE\\KEY_OraGI", SliceConstant.HEAD_OF_STRING_OR_LIST):
                logger.info("Get a grid!")
                ret, home_path = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE, registry,
                                              "ORACLE_HOME")
                if not ret:
                    logger.error("Cannot find home_env!")
                    return ScriptExitCode.ERROR_SCRIPT_ORACLEHOME_LOST, rac_info
                grid_home_path = home_path
                break
        if not grid_home_path:
            logger.error("Cannot find Grid information!")
            return RETURN_INVALID, rac_info

        # 获取cluster_name, cluster_ip, domain_name
        logger.info("Query cluster_name start!")
        cluster_name = get_rac_cluster_name(self._pid, grid_home_path)
        if not cluster_name:
            logger.error("Get cluster_name FAILED!")
            return RETURN_INVALID, rac_info
        logger.info("Query cluster_ip start!")
        ret_code, cluster_scan_ip = get_rac_cluster_scan_ip(self._pid, grid_home_path)
        if not ret_code:
            logger.error(f"Get cluster scan_ip and scan_name FAILED! pid = %s", self._pid)
            return RETURN_INVALID, rac_info
        logger.info("Query domain_name start!")
        ret_code, domain_names = get_rac_domain_name(self._pid, grid_home_path)
        if not ret_code:
            logger.error("Get domain_name FAILED!")
            return RETURN_INVALID, rac_info

        rac_info = {
            "cluster_type": db_type,
            "cluster_ip": cluster_scan_ip,
            "cluster_name": cluster_name,
            "domain_name": str(domain_names)
        }
        logger.info("Function oracle_rac_info SUCCESS!")
        return ErrorCode.SUCCESS.value, rac_info


def get_params_from_execute_windows_cmd(inst_name, cmd_list, input_cmd, param_name, pid):
    """
    从windows执行cmd命令输出的信息中获取参数，仅用于获取通过sqlplus获取到的参数
    因为cmd输出的信息格式为key \n --------- \n value的形式，因此可以采取统一的方式获取
    :param cd_path: (str)期望windows执行cmd命令需要切换到的路径，可以为空
    :param cmd_list: (list)cmd将要执行的命令
    :param input_cmd: (str)将要与cmd交互的命令
    :param param_name: (str)想要获取的参数key
    :param pid: (str)请求id
    """
    result = ""
    ret, low_auth_user = windows_switch_user(pid)
    if ret != ScriptExitCode.SUCCESS:
        logger.error("switch low auth user of windows failed. pid = %s", pid)
        return False, result
    try:
        os.environ["ORACLE_SID"] = inst_name
        os.environ["NLS_LANG"] = "American_America.US7ASCII"
        ret, output = execute_windows_cmd(pid, cmd_list, input_cmd)
        if not ret:
            logger.error("Get parameters FAILED! pid = %s", pid)
            return False, result
        output_list = output.split("\n")
        for i, _ in enumerate(output_list):
            if output_list[i].startswith(param_name, SliceConstant.HEAD_OF_STRING_OR_LIST):
                result = output_list[i + SliceConstant.INDEX_DIFFERENCE_BETWEEN_KEY_AND_VALUE]
                break
        logger.info("Get parameters SUCCESS! pid = %s", pid)
        return True, result
    finally:
        windows_switch_user_end(low_auth_user)


def get_db_name(pid, inst_name, db_user="", db_password=""):
    """
    获取数据库名
    :param pid: (str)请求id
    :param home_path: (str)想要查询数据库名的对应数据库主路径
    :param db_user: (str)数据库用户名
    :param db_password: (str)数据库密码
    """
    logger.info("Function get_db_name start! pid = %s", pid)
    result = ""
    ret, auth_type = get_auth_type(pid, inst_name)
    if not ret:
        logger.error("Cannot get auth_type! pid = %s", pid)
        return RETURN_INVALID, result
    if (auth_type == ShellAuthTypeEnum.OS_AUTH) and db_password:
        ret = check_database_password(pid, inst_name, db_user, db_password)
        if ret != ScriptExitCode.SUCCESS:
            logger.error("Did not pass the password check! pid = %s; ret = %d", pid, ret)
            return ret, result
    cmd_list = f"sqlplus /nolog".split()
    query_cmd = f'conn {db_user}/ as sysdba\n' \
                f'{db_password}\n' \
                f'show parameter db_name;'
    ret, result = get_params_from_execute_windows_cmd(inst_name, cmd_list, query_cmd, "NAME", pid)
    if not ret:
        logger.error("Get db_name FAILED! pid = %s", pid)
        return RETURN_INVALID, result
    if not result.strip():
        logger.warning(f"The database may not be opened or the auth_type is not matched! pid = {pid}")
        return "HoldON!!!", result
    database_name = result.split()[-1].lower()

    query_cmd = f'conn {db_user}/{db_password} as sysdba\n' \
                f"select value from v$parameter where name = 'db_unique_name';"
    ret, output = get_params_from_execute_windows_cmd(inst_name, cmd_list, query_cmd, "VALUE", pid)
    if not ret:
        logger.error("Get db_name FAILED! pid = %s", pid)
        return RETURN_INVALID, result
    database_unique_name = output.split()[-1]
    if database_name == database_unique_name:
        return ScriptExitCode.SUCCESS, database_name
    else:
        return ScriptExitCode.SUCCESS, database_unique_name


def check_database_password(pid, inst_name, db_user, db_password):
    logger.info("Function check_database_password start! pid = %s", pid)
    # sys用户有特殊情况
    if db_user == "sys":
        cmd_list = f"sqlplus /nolog".split()
        input_cmd = f'conn {db_user}/\n' \
                    f'{db_password}'
        os.environ["ORACLE_SID"] = inst_name
        os.environ["NLS_LANG"] = "American_America.US7ASCII"
        ret, output = execute_windows_cmd_with_low_auth_user(pid, cmd_list, input_cmd)
        if not ret:
            logger.error("Execute command FAILED! pid = %s", pid)
            return ScriptExitCode.ERROR_SCRIPT_EXEC_FAILED
        # ORA-01017为密码错误提示
        if "ORA-01017" in output:
            # 否则用户名密码不匹配，报错
            logger.error("Invalid password! pid = %s", pid)
            return ScriptExitCode.ERROR_DB_USERPWD_WRONG
        # ORA-28009证明用户名密码是匹配的，只是没有as sysdba/sysasm
        if "ORA-28009" in output:
            logger.info("Valid password! pid = %s", pid)
            return ScriptExitCode.SUCCESS
        if "ORA-01034" in output:
            logger.info("Database is NOT available! pid = %s", pid)
            # 使用错误实例，但是密码和用户名正确，会报这个错误，造成跳出循环无法校验其他实例，不应返回异常
            return ScriptExitCode.SUCCESS
        logger.info("Check password SUCCESS!")
        return ScriptExitCode.SUCCESS
    # 不是sys用户不需要检查
    logger.info("Valid password! pid = %s", pid)
    return ScriptExitCode.SUCCESS


def list_queried_database_home_env(pid, all_databases_registries: list, db_name: str, db_user: str = "",
                                   db_password: str = ""):
    """
    遍历注册表目录，感知到时数据库后，开始获取其db_name
    检验获取到的db_name是否和传递的db_name一致
    若一致，则把该数据库的home_path加入待返回的列表中
    :param pid: (str)请求id
    :param all_databases_registries: (list)给定注册表目录
    :param db_name: (str)数据库名，只有检查的数据库名与该入参一致才会被记入返回列表
    :param db_user: (str)数据库用户名
    :param db_password: (str)数据库密码
    :return: 错误码(int)，以及包含Oracle主路径的列表(list)
    """
    logger.info("Function list_queried_database_home_env start! pid = %s", pid)
    queried_database_home_env = []
    for registry in all_databases_registries:
        if registry.startswith("SOFTWARE\\ORACLE\\KEY_OraDB", SliceConstant.HEAD_OF_STRING_OR_LIST) or \
                registry.startswith("SOFTWARE\\ORACLE\\KEY_OraDb", SliceConstant.HEAD_OF_STRING_OR_LIST):
            # 获取ORACLE_HOME
            ret, oracle_home_path = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE,
                                                 registry, "ORACLE_HOME")
            if not ret:
                logger.warning(f"registry:{registry},Cannot find home_env! pid:{pid},")
                continue
            # 获取oracle home之后，到其database目录下，遍历找initXXX.ora文件，XXX为instance_name
            query_files_cmd = f"cmd /c dir /b {oracle_home_path}\\database".split()
            ret_code, files_string = execute_windows_cmd(pid, query_files_cmd, "")
            if not ret_code:
                logger.warning(f"{registry} cannot find the file related to the instance in oracle home!pid:{pid}")
                continue
            files_list = files_string.strip().splitlines()
            temp_instance_list = []
            temp_instance_list = get_inst_name_from_ora_file(files_list, temp_instance_list)
            res, inst = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE,
                                     registry, WindowsRegistryConstantString.ORACLE_SID)
            temp_instance_list.append(inst)
            # 通过set ORACLE_SID的方式进入数据库，然后查询数据库名
            # 如果获取的数据库名和给定的一致，则记录下来
            valid_db_input_param = {
                "pid": pid,
                "db_user": db_user,
                "db_password": db_password,
                "db_name": db_name,
                "oracle_home_path": oracle_home_path
            }
            ret, queried_database_home_env = record_valid_db(
                temp_instance_list, valid_db_input_param, registry, queried_database_home_env)
            if ret != ScriptExitCode.SUCCESS:
                logger.warning(f"{registry},get db_name FAILED!, pid = %s", pid)
                continue
            return ret, queried_database_home_env
    if not queried_database_home_env:
        logger.error(f"cannot find home_env! pid:{pid}")
        return RETURN_INVALID, queried_database_home_env
    logger.info(f"Function queried_database_home_env: {queried_database_home_env}. pid = %s", pid)
    return ScriptExitCode.SUCCESS, queried_database_home_env


def get_inst_name_from_ora_file(files_list, temp_instance_list):
    for temp_file in files_list:
        if temp_file.lower().startswith("init") and temp_file.lower().endswith(".ora"):
            temp_inst_name = temp_file[4:-4]
            temp_instance_list.append(temp_inst_name)
        if temp_file.lower().startswith("spfile") and temp_file.lower().endswith(".ora"):
            temp_inst_name = temp_file[6:-4]
            temp_instance_list.append(temp_inst_name)
    return temp_instance_list


def record_valid_db(temp_instance_list, valid_db_input_param, registry, queried_database_home_env):
    pid = valid_db_input_param.get("pid", "")
    db_user = valid_db_input_param.get("db_user", "")
    db_password = valid_db_input_param.get("db_password", "")
    db_name = valid_db_input_param.get("db_name", "")
    oracle_home_path = valid_db_input_param.get("oracle_home_path", "")

    logger.info(f"Function record_valid_db start! Pid = {pid}")
    for inst_name in temp_instance_list:
        ret, current_db_name = get_db_name(pid, inst_name, db_user, db_password)
        if ret == "HoldON!!!":
            logger.info(f"Current instance is invalid! Check the next one! Pid = {pid}")
            continue
        if ret != ScriptExitCode.SUCCESS:
            logger.error("Get db_name FAILED! pid = %s", pid)
            return ret, queried_database_home_env
        if current_db_name.lower() == db_name.lower():
            ret, oracle_base_path = get_registry(WindowsRegistryParentDirectory.HKEY_LOCAL_MACHINE, registry,
                                                 WindowsRegistryConstantString.ORACLE_BASE)
            queried_database_home_env.append({"oracle_home_path": oracle_home_path,
                                              "oracle_base_path": oracle_base_path,
                                              "oracle_sid": inst_name})
    return ScriptExitCode.SUCCESS, queried_database_home_env


def get_database_info(pid, inst_name, db_user="", db_password=""):
    """
    获取oracle数据库的基本信息，包括：
    1. 数据库实例名称
    2. 数据库状态
    3. 数据库版本
    4. 认证方式
    :param pid: (str)请求id
    :param home_path: (str)Oracle主路径
    :param db_user: (str)数据库用户名
    :param db_password: (str)数据库密码
    :return: 包含数据库信息的字典(dict)
    """
    logger.info("Function get_database_info start! pid = %s", pid)
    result_dict = {}

    ret, auth_type = get_auth_type(pid, inst_name)
    if not ret:
        logger.error("Get auth_type FAILED! pid = %s", pid)
        return result_dict

    # 获取数据库实例名称，状态，版本
    logger.info("Get instance_name, status and version of the database start! pid = %s", pid)
    sqlplus_cmd = f"sqlplus /nolog"
    query_cmd = f'conn {db_user}/ as sysdba\n' \
                f'{db_password}\n' \
                f'set line 200\n' \
                f'select instance_name, status, version from v$instance;'
    ret, output = get_params_from_execute_windows_cmd(inst_name, sqlplus_cmd.split(), query_cmd, "INSTANCE_NAME", pid)
    if not ret:
        logger.error("Get database basic info FAILED! pid = %s", pid)
        return result_dict
    result = output.split()
    result.append(auth_type)
    result_dict = {
        "instance_name": result[0],
        "status": result[1],
        "version": result[2],
        "auth_type": result[3]
    }
    minor_version = get_minor_version(pid, db_user, db_password, inst_name, sqlplus_cmd)
    if minor_version:
        result_dict["version"] = minor_version
    logger.info("Function get_database_info SUCCESS! pid = %s", pid)
    return result_dict


def get_minor_version(pid, db_user, db_password, inst_name, sqlplus_cmd):
    query_specific_version_cmd = f'conn {db_user}/ as sysdba\n' \
                                 f'{db_password}\n' \
                                 f'set line 200\n' \
                                 f"select TRIM(SUBSTR(BANNER_FULL, INSTR(BANNER_FULL, 'Version') + \
                                     LENGTH('Version '))) as version from v$version;"
    try:
        ret, minor_version = get_params_from_execute_windows_cmd(inst_name, sqlplus_cmd.split(),
                                                                 query_specific_version_cmd, "VERSION", pid)
        if not ret:
            logger.warning("The oracle version may be less than 19.")
            return None
    except Exception as e:
        logger.warning(f"The oracle version may be less than 19 or exception occurred : {e}")
        return None
    return minor_version


def get_auth_type(pid, inst_name, is_asm=False):
    auth_type = ""
    if is_asm:
        sqlplus_cmd = "sqlplus / as sysasm"
    else:
        sqlplus_cmd = "sqlplus / as sysdba"
    logger.info("Get auth_type start! pid = %s", pid)
    query_auth_cmd = r"SELECT instance_name from v$instance;"
    os.environ["ORACLE_SID"] = inst_name
    os.environ["NLS_LANG"] = "American_America.US7ASCII"
    ret, output = execute_windows_cmd_with_low_auth_user(pid, sqlplus_cmd.split(), query_auth_cmd)
    if not ret:
        logger.error("Get auth_type FAILED! pid = %s", pid)
        return False, auth_type
    if "Enter user-name:" in output.splitlines()[SliceConstant.TAIL_OF_STRING_OR_LIST]:
        auth_type = ShellAuthTypeEnum.DATABASE_AUTH
    else:
        auth_type = ShellAuthTypeEnum.OS_AUTH
    logger.info("Get auth_type SUCCESS! pid = %s", pid)
    return True, auth_type


def get_db_open_mode(pid, instance_name, db_user="", db_password=""):
    """
    获取数据库的open_mode状态
    :param pid: (str)请求id
    :param home_path: (str)Oracle主路径
    :param db_user: (str)数据库用户名
    :param db_password: (str)数据库密码
    :return: 数据库的open_mode状态(str)
    """
    logger.info("Function get_db_open_mode start! pid = %s", pid)
    result = ""
    sqlplus_cmd = f"sqlplus /nolog"
    query_cmd = f'conn {db_user}/ as sysdba\n' \
                f'{db_password}\n' \
                f'select open_mode from v$database;'
    ret, output = get_params_from_execute_windows_cmd(instance_name, sqlplus_cmd.split(), query_cmd, "OPEN_MODE", pid)
    if not ret:
        logger.error("Get open_mode FAILED! pid = %s", pid)
        return result
    logger.info("Function get_db_open_mode SUCCESS! pid = %s", pid)
    result = output[SliceConstant.HEAD_OF_STRING_OR_LIST:SliceConstant.TAIL_OF_STRING_OR_LIST]
    return result


def get_ip_and_port(pid, path, local_listener):
    """
    获取节点的ip以及端口
    :param pid: (str)请求id
    :param path: (str)Oracle主路径
    :param local_listener: 监听名
    :return: 返回错误码(int)，ip(str)以及端口(str)
    """
    logger.info("Function get_ip_and_port start! pid = %s", pid)
    host = ""
    port = ""
    listener_cmd = f"lsnrctl status {local_listener}"
    ret, output = execute_windows_cmd(pid, listener_cmd.split(), "", path)
    if not ret:
        logger.error("Get ip and port FAILED! pid = %s", pid)
        return False, host, port
    output_list = output.split("\n")

    target_string = ""
    for line in output_list:
        if r"(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)" in line:
            target_string = line
            break

    logger.info("Get host start! pid = %s", pid)
    host, port = get_ip_port_from_address(target_string)
    logger.info("Function get_ip_and_port SUCCESS! pid = %s", pid)
    return True, host, port


def get_asm_instance_name(pid):
    """
    获取ASM实例名
    :param pid: 请求id
    :return: 返回ASM实例名列表(list)
    """
    logger.info("Get asm instance name start! pid = %s", pid)
    result = []
    cmd = "sc query"
    ret, output = execute_windows_cmd(pid, cmd.split(), "")
    if not ret:
        logger.error("Execute sc query FAILED! pid = %s", pid)
        return False, result

    for item in output.splitlines():
        if "SERVICE_NAME: OracleASMService" in item:
            asm_name = item.split("OracleASMService")[SliceConstant.TAIL_OF_STRING_OR_LIST]
            result.append(asm_name)
    logger.info("Function get_asm_instance_name success! pid = %s", pid)
    return True, result


def get_rac_cluster_name(pid, path: str):
    logger.info("Function get_rac_cluster_name start!")
    cluster_name = ""
    home_path = os.path.join(path, "BIN")
    query_cmd = "cmd /c cemutlo -n"
    ret, output = execute_windows_cmd(pid, query_cmd.split(), "", home_path)
    if not ret:
        logger.error("Query cluster name FAILED!")
        return cluster_name
    cluster_name = output.split(":")[SliceConstant.TAIL_OF_STRING_OR_LIST]
    logger.info("Function get_rac_cluster_name SUCCESS!")
    return cluster_name


def get_rac_cluster_scan_ip(pid, path: str):
    logger.info("Function get_rac_cluster_ip start!")
    result_ip = ""
    home_path = os.path.join(path, "BIN")
    cmd_list = "cmd /c srvctl config scan".split()
    ret, output = execute_windows_cmd(pid, cmd_list, "", home_path)
    if not ret:
        logger.error(f"Get RAC cluster ip FAILED! pid = {pid}")
        return False, result_ip
    output_list = output.strip().splitlines()
    for item in output_list:
        if "VIP:" in item:
            result_ip = item.split(": ")[SliceConstant.TAIL_OF_STRING_OR_LIST]
            break
    logger.info("Function get_rac_cluster_ip SUCCESS!")
    return True, result_ip


def get_rac_domain_name(pid, path):
    logger.info("Function get_rac_domain_name start!")
    result = []
    home_path = os.path.join(path, "BIN")
    cmd = "cmd /c crsctl status server"
    ret, output = execute_windows_cmd(pid, cmd.split(), "", home_path)
    if not ret:
        logger.error("Query domain name FAILED!")
        return False, result
    output_list = output.strip().splitlines()

    for line in output_list:
        if line.startswith("NAME=", SliceConstant.HEAD_OF_STRING_OR_LIST):
            target_string = line.split("=")[SliceConstant.TAIL_OF_STRING_OR_LIST]
            result.append(target_string)
    logger.info("Function get_rac_domain_name SUCCESS!")
    return True, result
